package classedpack

import (
	"bytes"
	"compress/zlib"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"sync"
	"time"

	"gitee.com/thubcc/p2pgit/classify"
	"gitee.com/thubcc/p2pgit/constant"
	"gitee.com/thubcc/p2pgit/format"
	"gitee.com/thubcc/p2pgit/types"
	"gitee.com/thubcc/p2pgit/utils"
	"gopkg.in/src-d/go-git.v4/plumbing"
)

//Client http client
type Client struct {
	C       types.Hiter
	URL     string
	buf     *bytes.Buffer
	last    time.Time
	lock    sync.Mutex
	w       bool
	disable bool
}

func (c *Client) buildReq(s types.Selector) string {
	switch cl := c.C.(type) {
	case *classify.BasicClassify:
		return fmt.Sprintf("%s/np-%s?hash=%s", c.URL, cl.String(), s.Hex())
	case types.SomeHiter:
		return fmt.Sprintf("%s/get?hash=%s", c.URL, s.Hex())
	}
	return ""
}

// Hit interface
func (c *Client) Hit(h types.Hash) bool {
	return !c.disable && c.C.Hit(h)
}

// Name interface
func (c *Client) Name() (n string) {
	if c.disable {
		n = "~"
	}
	if bc, ok := c.C.(*classify.BasicClassify); ok {
		n += bc.String()
		return
	}
	return
}

// Reset reset has fuction
func (c *Client) Reset() error {
	return nil
}

//Get interface
func (c *Client) Get(s types.Selector) (uncompressed []byte, t plumbing.ObjectType, base *types.Header32, err error) {
	var hash types.Hash
	hash, err = types.Selector2Hash(s)
	if err != nil {
		return
	}
	if !c.C.Hit(hash) {
		err = types.ErrorNoFind
		return
	}
	var resp *http.Response
	resp, err = http.Get(c.buildReq(s))
	if err != nil {
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		var ret = make(map[string]string)
		dec := json.NewDecoder(resp.Body)
		dec.Decode(&ret)
		fmt.Println(ret, resp.StatusCode)
		if eof, ok := ret["error"]; ok {
			if eof != "EOF" {
				err = fmt.Errorf("%v", ret)
				return
			}
		}
	}
	var h32 *types.Header32
	h32, err = types.NewHeader32FromString(resp.Header.Get("Hash"))
	if err != nil {
		err = fmt.Errorf("hash:%s %v:%v", c.buildReq(s), resp.Header, err)
		return
	}
	t, err = plumbing.ParseObjectType(resp.Header.Get("Type"))
	if err != nil {
		err = fmt.Errorf("type:%v:%v", resp.Header, err)
		return
	}
	var length int
	length, err = strconv.Atoi(resp.Header.Get("Length"))
	if err != nil {
		err = fmt.Errorf("length:%v:%v", resp.Header, err)
		return
	}
	bs := resp.Header.Get("Base")
	if bs != "" {
		base, err = types.NewHeader32FromString(bs)
		if err != nil {
			err = fmt.Errorf("base:%v:%v", resp.Header, err)
			return
		}
	}
	bufw := new(bytes.Buffer)
	bufw.Reset()
	zr := utils.ZlibReaderPool.Get().(io.ReadCloser)
	defer utils.ZlibReaderPool.Put(zr)

	if err = zr.(zlib.Resetter).Reset(resp.Body, nil); err != nil {
		var serr = "no delta"
		if t.IsDelta() {
			serr = bs
		}
		err = fmt.Errorf("zlib reset error: %s, %s %s %s", err, h32.Hash.String(), t.String(), serr)
		return
	}

	defer zr.Close()
	buf := utils.ByteSlicePool.Get().([]byte)
	_, err = io.CopyBuffer(bufw, zr, buf)
	if err == io.EOF {
		err = nil
	}
	utils.ByteSlicePool.Put(buf)

	uncompressed = bufw.Bytes()
	if len(uncompressed) != length {
		err = fmt.Errorf("Wrony size, want %d, but %d", length, len(uncompressed))
	}
	return
}

//GetRaw interface
func (c *Client) GetRaw(s types.Selector) (raw []byte, err error) {
	var hash types.Hash
	hash, err = types.Selector2Hash(s)
	if err != nil {
		return
	}
	if !c.C.Hit(hash) {
		err = types.ErrorNoFind
		return
	}
	var resp *http.Response
	resp, err = http.Get(c.buildReq(s))
	if err != nil {
		return
	}
	defer resp.Body.Close()
	raw = make([]byte, resp.ContentLength)
	_, err = io.ReadFull(resp.Body, raw)
	return
}

// Open init client
func (c *Client) Open(w bool) error {
	c.w = w
	if w {
		c.buf = new(bytes.Buffer)
		go func() {
			for {
				<-time.After(time.Second)
				c.Close()
			}
		}()
	}
	return nil
}

// Sync flush client
func (c *Client) Sync() (err error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	var resp *http.Response
	if c.w && c.buf.Len() > 0 {
		send := c.buf.Bytes()
		var req string
		switch cl := c.C.(type) {
		case *classify.BasicClassify:
			req = fmt.Sprintf("%s/np-%s", c.URL, cl.String())
		case types.SomeHiter:
			req = fmt.Sprintf("%s/put", c.URL)
		}
		// fmt.Println(req)
		resp, err = http.Post(req, "application/octet-stream", c.buf)
		if err != nil {
			return
		}
		defer resp.Body.Close()
		// fmt.Println(req, resp)
		if resp.StatusCode != 200 {
			var ret interface{}
			dec := json.NewDecoder(resp.Body)
			dec.Decode(&ret)
			fmt.Println(ret, resp.StatusCode)
			format.TravelRaw(send, func(h *types.Header32, r []byte) error {
				if !h.Check() {
					fmt.Printf("crc fail: %s %s %08x %s %v\n", h.String(), "crc", h.CRC(), "check", h.Check())
				}
				return nil
			})
		}
	}
	return
}

// Close flush client
func (c *Client) Close() (err error) {
	return c.Sync()
}

// Remove disable
func (c *Client) Remove() (err error) {
	c.disable = true
	return c.Sync()
}

// Split split to two low level
func (c *Client) Split() error {
	return nil

	// bc, ok := c.C.(*classify.BasicClassify)
	// if !ok {
	// 	err = fmt.Errorf("Not Support: ")
	// 	return
	// }
	// var ops []types.Rawer
	// for i := 0; i < 2; i++ {
	// 	ops = append(ops, &Client{
	// 		C: &classify.BasicClassify{
	// 			Prefix: bc.Prefix*2 + uint64(i),
	// 			BitLen: bc.BitLen + 1,
	// 		},
	// 		URL: c.URL,
	// 	})
	// 	err = ops[i].Open(true)
	// 	if err != nil {
	// 		return
	// 	}
	// }
	// var r types.Rawer = types.NewRawer(ops)
	// rs = &r
	// return
}

// Put interface
func (c *Client) Put(raw []byte) (err error) {
	r := bytes.NewReader(raw)
	var header *format.Header
	header, err = format.ExtFromFile(r)
	if err != nil {
		return
	}
	if !c.C.Hit(header.Header32.Hash) {
		err = constant.ErrorNoMatch
		return
	}
	if len(raw) != int(header.Header32.Size)+32 {
		err = fmt.Errorf("bad length: want %d, but %d", int(header.Header32.Size)+32, len(raw))
		return
	}
	var unc []byte
	var base *types.Header32
	unc, _, base, err = format.Parse(raw)
	if header.OHeader.Type.IsDelta() && base == nil {
		err = fmt.Errorf("delta object miss base: %s", header.OHeader.Type.String())
		return
	}
	if len(unc) != int(header.OHeader.Length) {
		err = fmt.Errorf("bad uncompressed object size: want %d, but %d", header.OHeader.Length, len(unc))
		return
	}
	return c.put(raw)
}

func (c *Client) put(raw []byte) (err error) {
	if int64(len(raw)+c.buf.Len()) > constant.MaxHTTPPostSize {
		err = c.Close()
		if err != nil {
			return
		}
	}
	var n int
	n, err = c.buf.Write(raw)
	if err != nil {
		return
	}
	if n != len(raw) {
		err = fmt.Errorf("Can not write: want %d, but %d", len(raw), n)
	}
	return
}

var (
	exampleAPI = "np-08-0000000000000003"
)

//CheckAPIName check api name
func CheckAPIName(base string) (err error) {
	if len(exampleAPI) != len(base) {
		err = fmt.Errorf("Bad api name(length): %s", base)
		return
	}
	if base[:3] != examplePack[:3] {
		err = fmt.Errorf("Bad api name(start with np-): %s", base)
		return
	}
	return nil
}

// NewAPI new client
func NewAPI(url string, h types.Hiter) (ag *Client, err error) {
	ag = &Client{
		C:   h,
		URL: url,
	}
	return
}

// NewBasicAPI new client
func NewBasicAPI(url string, api string) (ag *Client, err error) {
	var c types.Classifer
	c, err = classify.NewBasicClassifyFromString(api[3:])
	if err != nil {
		return
	}
	ag = &Client{
		C:   c,
		URL: url,
	}
	return
}
